﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace Isuka.Iteration.Auxiliaries
{
    public sealed class WrappedIterator<TSrc, TDst>
        : IIterator<TDst>
    {
        IIterator<TSrc> it;
        readonly IIterable<IIterator<TSrc>> source;
        readonly IIteratorTraits<TSrc, TDst> traits;

        public WrappedIterator(IIterable<IIterator<TSrc>> source
            , IIterator<TSrc> it
            , IIteratorTraits<TSrc, TDst> traits)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(it != null, "it");
            Contract.Requires<ArgumentNullException>(traits != null, "trais");

            this.it = it;
            this.source = source;
            this.traits = traits;
        }

        bool EqualsCore(WrappedIterator<TSrc, TDst> other)
        {
            Contract.Requires(other != null);
            return it.Equals(other.it) && traits.Equals(other.traits);
        }

        #region IIterator<TDst> メンバー

        public TDst Current
        {
            get { return traits.Map(it.Current); }
        }

        #endregion

        #region IIterator メンバー

        public bool Advance(int diff)
        {
            Gate.Positive(diff);
            while (diff != 0 && InRange)
            {
                MoveNext();
                diff--;
            }
            return diff == 0;
        }

        public int? Bias
        {
            get { return it.Bias; }
        }

        public object Container
        {
            get { return source; }
        }

        object IIterator.Current
        {
            get { return Current; }
        }

        public int? Distance(IIterator other)
        {
            if (other is WrappedIterator<TSrc, TDst> || other.GetType() == source.GetType())
            {
                var result = 0;
                for (var i = this.Copy(); i.InRange && i.Equals(other); i.MoveNext(), result++) ;
                return result;
            }
            return null;
        }

        public bool Equals(IIterator other)
        {
            if (other == null)
            {
                return !InRange;
            }
            else
            {
                if (other is WrappedIterator<TSrc, TDst>)
                {
                    return EqualsCore((WrappedIterator<TSrc, TDst>)other);
                }
            }
            return false;
        }

        public bool InRange
        {
            get { return source.InRange(it); }
        }

        public void MoveNext()
        {
            if (source.MoveNext(it))
            {
                while (InRange && !traits.Filter(it.Current))
                {
                    it.MoveNext();
                }
            }
        }

        #endregion

        #region ICloneable メンバー

        public object Clone()
        {
            return new WrappedIterator<TSrc, TDst>(source, it, traits);
        }

        #endregion

        #region Object メンバー

        // override object.Equals
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return !InRange;
            }
            else
            {
                if (obj is WrappedIterator<TSrc, TDst>)
                {
                    return EqualsCore((WrappedIterator<TSrc, TDst>)obj);
                }
            }
            return false;
        }

        // override object.GetHashCode
        public override int GetHashCode()
        {
            return source.GetHashCode() ^ traits.GetHashCode();
        }

        #endregion

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(it != null);
            Contract.Invariant(source != null);
            Contract.Invariant(traits != null);
        }

        #endregion
    }

}
